/******************************************************************************
 * Copyright 2020 The Firmament Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/

#include <firmament.h>

#include "qx_drv_systick.h"
#include "device.h"
#include "hal/systick/systick.h"
#include "cpu_port.h"


static systick_dev_t systick_dev;

void qx_timer_config(rt_uint32_t base, rt_uint32_t period, rt_uint16_t prescaler, rt_uint8_t int_flag)
{
    CPUTimer_stopTimer(base);
    CPUTimer_setPeriod(base, period);
    CPUTimer_setPreScaler(base, prescaler);

    if (int_flag)
    {
        if (base == CPUTIMER0_BASE)
            Interrupt_enable(INT_TIMER0);
        else if (base == CPUTIMER1_BASE)
            Interrupt_enable(INT_TIMER1);
        else
            Interrupt_enable(INT_TIMER2);
        CPUTimer_enableInterrupt(base);
    }
    else
    {
        if (base == CPUTIMER0_BASE)
            Interrupt_disable(INT_TIMER0);
        else if (base == CPUTIMER1_BASE)
            Interrupt_disable(INT_TIMER1);
        else
            Interrupt_disable(INT_TIMER2);
        CPUTimer_disableInterrupt(base);
    }
}

void rt_thread_system_tick_init(rt_uint32_t tick)
{
    qx_timer_config(CPUTIMER2_BASE, tick, 0, 1);

    CPUTimer_startTimer(CPUTIMER2_BASE);
}


static void _set_systick_freq(rt_uint32_t freq)
{
    rt_uint32_t ClockFreq;
    rt_uint32_t TicksNum;

    RT_ASSERT(freq > 0);
    RT_ASSERT(systick_dev != NULL);

    SystemCoreClock = SysCtl_getClock(DEVICE_OSCSRC_FREQ);

    ClockFreq = SystemCoreClock;
    TicksNum = ClockFreq / freq;

    systick_dev->ticks_per_us = ClockFreq / 1e6;
    systick_dev->ticks_per_isr = TicksNum;

    rt_thread_system_tick_init(TicksNum);
}

static rt_err_t systick_configure(systick_dev_t systick, struct systick_configure* cfg)
{
    _set_systick_freq(cfg->tick_freq);

    systick->config = *cfg;

    return RT_EOK;
}

// TODO: return ticks number instead of us
static rt_uint32_t systick_read(systick_dev_t systick)
{
    return (CPUTimer_getTimerCount(CPUTIMER2_BASE)) / systick->ticks_per_us;
}

const static struct systick_ops _systick_ops = {
    systick_configure,
    systick_read
};

__interrupt void rt_thread_tick_intr(void)
{
    /* enter interrupt */
    rt_interrupt_enter();

    hal_systick_isr(systick_dev);

    rt_tick_increase();

    /* leave interrupt */
    rt_interrupt_leave();

    CPUTimer_clearInterrupt(CPUTIMER2_BASE);
}

rt_err_t drv_systick_init(void)
{
    static struct systick_device dev = {
        .ops = &_systick_ops,
        .config = SYSTICK_CONFIG_DEFAULT,
        .systick_isr_cb = RT_NULL
    };
    systick_dev = &dev;

    _set_systick_freq(dev.config.tick_freq);

    return hal_systick_register(systick_dev, "systick", RT_DEVICE_FLAG_RDONLY, RT_NULL);
}
